package xdb;

import com.goldhuman.Common.Marshal.MarshalException;
import com.goldhuman.Common.Marshal.OctetsStream;
import com.goldhuman.Common.Octets;
import mysql.MySqlMgr;
import xdb.TRecord.State;

import java.sql.SQLException;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 存储层接口
 *
 * <ul>
 * <li> 修改操作不加锁，查询操作加锁。
 * <li>exist,find,walk <b>表锁</b>
 * <li>firstKey, nextKey <b>表锁</b>
 * <li>_walk, _find <b>表锁</b>
 * <li>replaceUnsafe,insertUnsafe,removeUnsafe <b>表锁</b>
 * <li>replace, insert, remove <b>无锁</b>
 * <li>_replace, _insert <b>无锁</b>
 * <li>browse <b>无锁，不能和checkpoint并发</b>
 * </ul>
 *
 * <p> Storage实际上不需要是 abstract。由于它的一些方法在实现 xdb 时，必须重载。
 * 在 Storage 进行修改和调试时，这些方法会被声明为 abstract，便于发现是否有重载漏掉。
 *
 * @see 全局锁定规则：docs/lock.mht
 * @see share/commom/db.h
 */
public abstract class Storage {
    private static final String HEX_CHARS = "0123456789abcdef";
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final String tableName;

    public Storage(XdbConf xconf, TableConf tconf) {
        this(tconf.getName(), tconf.getCacheHigh(), tconf.getCacheLow());
    }

    public Storage(String tableName, int cacheHigh, int cacheLow) {
        // 记住数据表文件。防止运行中配置被修改。
        this.tableName = tableName;
        String sql = null;
        if (Xdb.getInstance().isJson()) {
            sql = "CREATE TABLE IF NOT EXISTS " + tableName
                    + "(k VARCHAR(255) NOT NULL PRIMARY KEY, v JSON NOT NULL) ENGINE=INNODB DEFAULT CHARSET=UTF8";
        } else {
            sql = "CREATE TABLE IF NOT EXISTS " + tableName
                    + "(k VARCHAR(255) NOT NULL PRIMARY KEY, v MEDIUMBLOB NOT NULL) ENGINE=INNODB DEFAULT CHARSET=UTF8";
        }
        if (MySqlMgr.GetInstance().isAutoSql()) {
            int ret = MySqlMgr.GetInstance().ExecUpdate(sql, null);
            if (ret < 0) {
                throw new XError("create table faild : " + tableName);
            }
        }
    }

    public static final byte[] getValueBytes(byte[] bytes, int size) {
        byte[] tmp = new byte[size];
        System.arraycopy(bytes, 0, tmp, 0, size);
        return tmp;
    }

    public static final String getHexBytes(byte[] bytes, int length) {
        if (bytes == null || length == 0) {
            return "";
        }
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            sb.append(HEX_CHARS.charAt(bytes[i] >> 4 & 0xf));
            sb.append(HEX_CHARS.charAt(bytes[i] & 0xf));
        }
        return sb.toString();
    }

    public static final byte[] getBytesByHexString(String hexStr) {
        if (hexStr == null || hexStr == "") {
            xdb.Trace.error("hexStr is null or hexStr.length is 0");
            return null;
        }
        if (hexStr.length() % 2 != 0) {
            xdb.Trace.error("hexStr.length=" + hexStr.length() + " hexStr=" + hexStr);
            return null;
        }
        byte[] bytes = new byte[hexStr.length() / 2];
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = (byte) (0xff & Integer.parseInt(hexStr.substring(2 * i, 2 * i + 2), 16));
        }
        return bytes;
    }

    public static final String getStringByHexString(String hexStr) {
        byte[] bs = TStorage.getBytesByHexString(hexStr);
        Octets oct = new Octets(bs);
        OctetsStream octStream = new OctetsStream(oct);
        try {
            return octStream.unmarshal_String("UTF-16LE");
        } catch (MarshalException e) {
            e.printStackTrace();
        }
        return "";
    }

    public final void lock() {
        rwLock.writeLock().lock();
    }

    public final void unlock() {
        rwLock.writeLock().unlock();
    }

    ///////////////////////////////////////////////////////////////
    // checkpoint: marshalN -> lock(w){ marshal0 + snapshot } -> flush -> checkpoint
    int marshalN() {
        return 0;
    }

    int marshal0() {
        return 0;
    }

    int snapshot() {
        return 0;
    }

    int flush(long lastFlushTime) {
        return 0;
    }

    public final String getTableName() {
        return tableName;
    }

    //////////////////////////////////////////////////////////////////
    // 数据存取接口。

    /**
     * 测试key是否存在。<b>锁：Locked</b>。
     *
     * @throws SQLException
     */
    public final boolean exist(OctetsStream key) throws SQLException {
        return find(key) != null;
    }

    /**
     * 查找并返回记录。如果不存在则返回null。<b>锁：Locked</b>。
     *
     * @throws SQLException
     */
    public final OctetsStream find(OctetsStream key) throws SQLException {
        rwLock.readLock().lock();
        try {
            String cacheKey = getHexBytes(key.array(), key.size());
            if (!Xdb.getInstance().closeCache()) {
                boolean existInCache = Xdb.getInstance().getCheckpointcache().existInCache(tableName, cacheKey);
                if (existInCache) {
                    return Xdb.getInstance().getCheckpointcache().find(tableName, cacheKey);
                }
            }
            String sql = "SELECT v FROM " + tableName + " WHERE k='" + cacheKey + "'";
            return MySqlMgr.GetInstance().ExecQuery(sql);
        } finally {
            rwLock.readLock().unlock();
        }
    }

    /**
     * 插入记录，如果存在则覆盖，如果不存在则添加。<b>锁：Locked</b>。
     *
     * @throws 通过异常报告错误。
     */
    public final void replaceUnsafe(OctetsStream key, OctetsStream value, long flushTime) {
        if (Xdb.getInstance().closeCache()) {
            return;
        }
        rwLock.writeLock().lock();
        try {
            Xdb.getInstance().getCheckpointcache().flushRecord(tableName, getHexBytes(key.array(), key.size()), State.INDB_GET, value, flushTime);
			/*
			String sql = "replace into " + tableName + "(k,v) values (\'" + getHexBytes(key.array(), key.size()) + "\', ?)";
			byte[] v = getValueBytes(value.array(), value.size());
			int ret = MySqlMgr.GetInstance().ExecUpdate(sql, v);
			if(ret < 0) {
				if(MySqlMgr.GetInstance().ExecUpdate(sql, v) < 0) {
					// 两次执行失败，放入队列
					queueSQL.offer(new Pair<String, byte[]>(sql, v));
				}
			}
			*/
        } finally {
            rwLock.writeLock().unlock();
        }
    }

    /**
     * 插入记录，如果记录已经存在则抛出异常。<b>锁：Locked</b>。
     *
     * @throws 通过异常报告错误。
     */
    public final void insertUnsafe(OctetsStream key, OctetsStream value, long flushTime) {
        if (Xdb.getInstance().closeCache()) {
            return;
        }
        rwLock.writeLock().lock();
        try {
            Xdb.getInstance().getCheckpointcache().flushRecord(tableName, getHexBytes(key.array(), key.size()), State.ADD, value, flushTime);
			/*
			String sql = "insert into " + tableName + "(k,v) values (\'" + getHexBytes(key.array(), key.size()) + "\', ?)";
			byte[] v = getValueBytes(value.array(), value.size());
			int ret = MySqlMgr.GetInstance().ExecUpdate(sql, v);
			if(ret < 0) {
				if(MySqlMgr.GetInstance().ExecUpdate(sql, v) < 0) {
					// 两次执行失败，放入队列
					queueSQL.offer(new Pair<String, byte[]>(sql, v));
				}
			}
			*/
        } finally {
            rwLock.writeLock().unlock();
        }
    }

    /**
     * 删除记录。如果记录不存在则抛出异常。<b>锁：Locked</b>。
     *
     * @throws 通过异常报告错误。
     */
    public final void removeUnsafe(OctetsStream key, long flushTime) {
        if (Xdb.getInstance().closeCache()) {
            return;
        }
        rwLock.writeLock().lock();
        try {
            Xdb.getInstance().getCheckpointcache().flushRecord(tableName, getHexBytes(key.array(), key.size()), State.INDB_REMOVE, null, flushTime);
			/*
			String sql = "delete from " + tableName + " where k=\'" + getHexBytes(key.array(), key.size()) + "\'";
			int ret = MySqlMgr.GetInstance().ExecUpdate(sql, null);
			if(ret < 0) {
				if(MySqlMgr.GetInstance().ExecUpdate(sql, null) < 0) {
					// 两次执行失败，放入队列
					queueSQL.offer(new Pair<String, byte[]>(sql, null));
				}
			}
			*/
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}
